﻿using System;
using System.Collections.Generic;
using System.Xml;
using System.IO;

using Commons = NougakudoCompanion.Commons;
using NougakuDoController;

namespace NougakudoCompanion.Commons
{
    public partial class Runtime
    {

        List<string> downloads;
        string nougakudo_new;
        Dictionary<string, Commons.Application> applications_new;
        internal List<string> Downloads { get { return downloads; } }

        internal bool DownloadableRuntime
        {
            get
            {
                if (downloads == null) return false;
                return downloads.Contains(environment.Name);
            }
        }

        internal static Tuple<bool, Runtime> CreateInstance()
        {
            Runtime runtime = new Runtime();
            runtime.SetupForController();
            bool ret;
            try
            {
                ret = runtime.GetAzureEnvironment();

                ret = runtime.LoadConfiguration(false);
            }
            catch (Exception e)
            {
                Logging.Write("Runtime:Initialize error {0}.", new object[] { e.Message }, Logging.LogType.ERROR);
                return new Tuple<bool, Runtime>(false, runtime);
            }
            return new Tuple<bool, Runtime>(ret, runtime);
        }

        void SetupForController()
        {
            this.GetAzureEnvironment = GetAzureEnvironmentForController;
            this.LoadConfiguration = LoadConfigurationForController;
        }

        bool GetAzureEnvironmentForController()
        {
            var doc = new XmlDocument();
            var data = GetXmlString(Commons.Constants.AZURE_CONFIGURATION);
            if (data == "")
            {
                //throw new Exception("Runtime:Not found " + Commons.Constants.AZURE_CONFIGURATION);
                return false;
            }
            doc.LoadXml(data);
            try
            {
                var runtime = doc.SelectSingleNode("/azure/runtime");
                var folder = runtime.SelectSingleNode("runtimefolder").InnerText;
                var nameNode = runtime.SelectSingleNode("name");
                var name = nameNode.InnerText;
                var appfolder = runtime.SelectSingleNode("applicationfolder").InnerText;
                var downfolder = runtime.SelectSingleNode("downloadfolder").InnerText;
                var rolefolder = runtime.SelectSingleNode("roleroot").InnerText;
                var instanceid = runtime.SelectSingleNode("roleinstance").InnerText;
                var interval = int.Parse(runtime.SelectSingleNode("interval").InnerText);
                var developmentfabric = bool.Parse(runtime.SelectSingleNode("usedevelopmentfabric").InnerText);
                var virtualHost = runtime.SelectSingleNode("virtualhost").InnerText;    // Add for NougakuDo 1.1.0 above.

                var blob = doc.SelectSingleNode("/azure/blob");
                var account = blob.SelectSingleNode("account").InnerText;
                var sharedKey = blob.SelectSingleNode("sharedkey").InnerText;
                var container = blob.SelectSingleNode("container").InnerText;

                folder = Path.GetFullPath(folder);
                appfolder = Path.GetFullPath(appfolder);
                downfolder = Path.GetFullPath(downfolder);

                // Add temp folder
                var tempfolder = runtime.SelectSingleNode("tempfolder").InnerText;
                tempfolder = Path.GetFullPath(tempfolder);

                environment = new Commons.Environment
                {
                    RuntimeFolder = folder,
                    ApplicationFolder = appfolder,
                    DownloadFolder = downfolder,
                    Name = name,
                    RoleRoot = rolefolder,
                    RoleInstanceID = instanceid,
                    Interval = interval,
                    Account = account,
                    SharedKey = sharedKey,
                    BlobContainer = container,
                    UseDevelopmentFabric = developmentfabric,
                    VirtualHost = virtualHost,   // Add for NougakuDo 1.1.0 above.
                    TempFolder = tempfolder     // Add temp folder
                };
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Load configuration.xml, then load newconfiguration.xml. Check diff between config and new config, then make download list.
        /// But when recovery mode, no load new config.
        /// </summary>
        /// <param name="recovery">true is recovery mode for install error.</param>
        /// <returns></returns>
        bool LoadConfigurationForController(bool recovery = false)
        {
            bool ret = CheckConfiguration();
            if (ret)
            {
#if DEBUG
                Logging.Write("Runtime:Found {0}.", new object[] { Commons.Constants.CONFIGURATION }, Logging.LogType.INFO);
#endif
            }
            else
            {
                Logging.Write("Runtime:Not download {0}.", new object[] { Commons.Constants.CONFIGURATION }, Logging.LogType.ERROR);
                throw new Exception("Load Configuration Error");
            }

            //ret = GetExsistConfiguration();
            var config = GetConfiguration(Commons.Constants.CONFIGURATION);

            if (config.Item1 != "")
            {
                environment.Version = config.Item1;
                applications = config.Item2;
                Logging.Write("Runtime:Loaded {0}.", new object[] { Commons.Constants.CONFIGURATION }, Logging.LogType.INFO);
            }
            else
            {
                Logging.Write("Runtime:Not load {0}.", new object[] { Commons.Constants.CONFIGURATION }, Logging.LogType.ERROR);
                throw new Exception("Load Exsiting configuration Error");
            }
            // recovery mode, skip to check diff between config and new config.
            if (recovery) return true;
            // normal mode, then check diff between config and new config.
            ret = LoadNewConfiguration();
            if (ret || File.Exists(Commons.Constants.NEW_CONFIG))
            {
                Logging.Write("Runtime:Loaded new {0}.", new object[] { Constants.NEW_CONFIG }, Logging.LogType.INFO);
            }
            else
            {
                Logging.Write("Runtime:Not load new {0}.", new object[] { Commons.Constants.NEW_CONFIG }, Logging.LogType.INFO);
                CreateDownloadList();
                ret = GetDownload();
                if (ret)
                {
                    Logging.Write("Runtime:Found download applications request.", Logging.LogType.INFO);
                }
                else
                {
                    Logging.Write("Runtime:Not Found download applications request.", Logging.LogType.INFO);
                }
            }

            return true;
        }


        /// <summary>
        /// If not exsist configuration file, download it from blob. 
        /// </summary>
        /// <returns>success</returns>
        bool CheckConfiguration()
        {
            if (!File.Exists(Commons.Constants.CONFIGURATION))
            {
                Logging.Write("Runtime:Start download {0}.", new object[] { Commons.Constants.CONFIGURATION }, Logging.LogType.INFO);
                var downloader = new Downloader(this);
                var ret = downloader.GetConfiguration();
                if (ret.Item1)
                {
#if DEBUG
                    Logging.Write("Runtime:Complete download {0}.", new object[] { Commons.Constants.CONFIGURATION }, Logging.LogType.INFO);
#endif
                    var source = Path.Combine(environment.DownloadFolder, Commons.Constants.CONFIGURATION);
                    File.Copy(source, Commons.Constants.CONFIGURATION);
                    File.Delete(source);
                }
                else
                {
                    Logging.Write("Runtime:Download Error {0} : {1}.", new object[] { Commons.Constants.CONFIGURATION, ret.Item2 }, Logging.LogType.INFO);
                    return false;
                }
            }

            return true;
        }



        internal bool LoadNewConfiguration()
        {
            var ret = GetNewConfiguration();
            if (ret)
            {
                Logging.Write("Runtime:Loaded new {0}.", new object[] { Commons.Constants.NEW_CONFIG }, Logging.LogType.INFO);
            }
            else
            {
                Logging.Write("Runtime:Not load new {0}.", new object[] { Commons.Constants.NEW_CONFIG }, Logging.LogType.ERROR);
                return false;
            }
            CreateDownloadList();
            ret = GetDownload();
            if (ret && downloads.Count != 0)
            {
                Logging.Write("Runtime:Found download applications request.", Logging.LogType.INFO);
            }
            else
            {
                // no need update
                Logging.Write("Runtime:Not Found download applications request.", Logging.LogType.INFO);
                return true;
            }

            CopyConfiguration(applications_new, applications);

            environment.Version = nougakudo_new;
            nougakudo_new = "";
            applications_new = null;

            return true;
        }

        bool GetNewConfiguration()
        {
            nougakudo_new = "";
            applications_new = null;
            if (!File.Exists(Commons.Constants.NEW_CONFIG))
            {
                return false;
            }
            var config = GetConfiguration(Commons.Constants.NEW_CONFIG);
            if (config == null)
            {
                return false;
            }

            nougakudo_new = config.Item1;
            applications_new = config.Item2;
            return true;
        }

        void CreateDownloadList()
        {

            var apps = new List<string>();
            // add NougakuDo runtime
            if (environment.Version != nougakudo_new)
                apps.Add(environment.Name);

            Dictionary<string, Commons.Application> primary = applications;
            if (applications_new != null)
                primary = applications_new;
            foreach (var app in primary.Keys)
            {
                if (applications_new != null)
                {
                    if (!applications.ContainsKey(app))
                    {
                        apps.Add(app);
                    }
                    else if (applications_new[app].Version != applications[app].Version)
                    {
                        apps.Add(app);
                    }
                }
                else
                {
                    apps.Add(app);
                }
            }

            CreateDownloadData(apps);

        }

        void CreateDownloadData(List<string> apps)
        {
            var doc = new XmlDocument();
            var root = doc.CreateElement("downloads");
            var dec = doc.CreateXmlDeclaration("1.0", "utf-8", "yes");
            foreach (var app in apps)
            {
                var down = doc.CreateElement("download");
                down.InnerText = app;
                root.AppendChild(down);
            }

            doc.AppendChild(root);
            doc.InsertBefore(dec, root);

            if (File.Exists(Commons.Constants.DOWNLOAD_LIST))
            {
                File.Delete(Commons.Constants.DOWNLOAD_LIST);
            }

            doc.Save(Commons.Constants.DOWNLOAD_LIST);
        }

        internal bool GetDownload()
        {
            downloads = new List<string>();
            var doc = new XmlDocument();
            var data = GetXmlString(Commons.Constants.DOWNLOAD_LIST);
            if (data == "") return false;
            doc.LoadXml(data);

            var apps = doc.SelectNodes("/downloads/download");
            if (apps == null) return false;
            foreach (XmlNode i in apps)
            {
                downloads.Add(i.InnerText);
            }

            return true;
        }

        void CopyConfiguration(Dictionary<string, Commons.Application> fromApps,
                       Dictionary<string, Commons.Application> toApps)
        {
            toApps.Clear();
            foreach (var i in fromApps.Keys)
            {
                toApps[i] = fromApps[i];
            }
            fromApps.Clear();
            if (File.Exists(Commons.Constants.CONFIGURATION_BACKUP))
                File.Delete(Commons.Constants.CONFIGURATION_BACKUP);
            File.Move(Commons.Constants.CONFIGURATION, Commons.Constants.CONFIGURATION_BACKUP);
            File.Copy(Commons.Constants.NEW_CONFIG, Commons.Constants.CONFIGURATION, true);
        }

    }
}
